/*
 * Copyright (c) 2022 Diemit <598757652@qq.com>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdlib.h>
#include "hcs_macro.h"
#include "hdf_config_macro.h"
#include "hdf_device_desc.h"
#include "hdf_log.h"
#include "i2c_core.h"
#include "i2c_if.h"
#include "osal_mutex.h"
#include "driver/i2c.h"

#ifndef LOSCFG_DRIVERS_HDF_CONFIG_MACRO
#error "LOSCFG_DRIVERS_HDF_CONFIG_MACRO"
#endif

#define I2C_INVALID_ADDR 0xFFFF
#define HAL_I2C_ID_NUM 1

#define ACK_CHECK_EN (0x1)       /*!< I2C master will check ack from slave */
#define ACK_VAL (0x0)            /*!< I2C ack value */
#define NACK_VAL (0x1)           /*!< I2C nack value */

struct I2cResource {
    uint8_t mode;
    uint8_t port;
    uint8_t scl_pin;
    uint8_t sda_pin;
    uint32_t speed;
};

struct I2cDevice {
    uint8_t mode;
    uint16_t devAddr;      /**< slave device addr */
    uint32_t addressWidth; /**< Addressing mode: 7 bit or 10 bit */
    struct OsalMutex mutex;
    uint32_t port;
    struct I2cResource resource;
};

/* HdfDriverEntry method definitions */
static int32_t I2cDriverInit(struct HdfDeviceObject *device);
static int32_t I2cDriverBind(struct HdfDeviceObject *device);
static void I2cDriverRelease(struct HdfDeviceObject *device);

/* HdfDriverEntry definitions */
static const struct HdfDriverEntry g_i2cDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "ESP32_I2C_MODULE_HDF",
    .Bind = I2cDriverBind,
    .Init = I2cDriverInit,
    .Release = I2cDriverRelease,
};

// Initialize HdfDriverEntry
HDF_INIT(g_i2cDriverEntry);

/* I2cHostMethod method definitions */
static int32_t I2cHostTransfer(struct I2cCntlr *cntlr, struct I2cMsg *msgs, int16_t count);

struct I2cMethod g_i2cHostMethod = {
    .transfer = I2cHostTransfer,
};

#define I2C_FIND_CONFIG(node, name, resource, result) \
    do { \
        if (strcmp(HCS_PROP(node, match_attr), name) == 0) { \
            resource->mode   = HCS_PROP(node, mode); \
            resource->port = HCS_PROP(node, port); \
            resource->scl_pin = HCS_PROP(node, scl_pin); \
            resource->sda_pin = HCS_PROP(node, sda_pin); \
            resource->speed  = HCS_PROP(node, speed); \
            result = HDF_SUCCESS; \
        } \
    } while (0)
#define PLATFORM_CONFIG HCS_NODE(HCS_ROOT, platform)
#define PLATFORM_I2C_CONFIG HCS_NODE(HCS_NODE(HCS_ROOT, platform), i2c_config)
static uint32_t GetI2cDeviceResource(struct I2cDevice *device,
                                     const char *deviceMatchAttr)
{
    int32_t result = HDF_FAILURE;
    struct I2cResource *resource = NULL;
    if (device == NULL || deviceMatchAttr == NULL) {
        HDF_LOGE("device or deviceMatchAttr is NULL");
        return HDF_ERR_INVALID_PARAM;
    }
    resource = &device->resource;
#if HCS_NODE_HAS_PROP(PLATFORM_CONFIG, i2c_config)
    HCS_FOREACH_CHILD_VARGS(PLATFORM_I2C_CONFIG, I2C_FIND_CONFIG, deviceMatchAttr, resource, result);
#endif
    if (result != HDF_SUCCESS) {
        HDF_LOGE("resourceNode %s is NULL", deviceMatchAttr);
    }
    return result;
}

int32_t InitI2cDevice(struct I2cDevice *device)
{
    uint32_t i2cPort;
    struct I2cResource *resource = NULL;
    
    if (device == NULL) {
        HDF_LOGE("device is NULL\r\n");
        return HDF_ERR_INVALID_PARAM;
    }
    
    resource = &device->resource;
    device->port = resource->port;
    i2cPort = device->port;
    if (i2cPort >= HAL_I2C_ID_NUM) {
        HDF_LOGE("i2c port %u not support\r\n", i2cPort);
        return HDF_ERR_NOT_SUPPORT;
    }

    // if (OsalMutexInit(&device->mutex) != HDF_SUCCESS) {
    //     HDF_LOGE("%s %d OsalMutexInit fail\r\n", __func__, __LINE__);
    //     return HDF_FAILURE;
    // }

    // if (HDF_SUCCESS != OsalMutexLock(&device->mutex)) {
    //     HDF_LOGE("%s %d osMutexWait fail\r\n", __func__, __LINE__);
    //     return HDF_ERR_TIMEOUT;
    // }

    if (resource->scl_pin != 0 || resource->sda_pin != 0) {
        i2c_config_t i2c_conf = {0};
        i2c_conf.mode = I2C_MODE_MASTER;
        i2c_conf.scl_io_num = resource->scl_pin;
        i2c_conf.sda_io_num = resource->sda_pin;
        i2c_conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
        i2c_conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
        i2c_conf.master.clk_speed = resource->speed;

        esp_err_t ret = i2c_param_config(i2cPort, &i2c_conf);
        if (ret != ESP_OK) {
            HDF_LOGE("%s %d i2c config fail\r\n", __func__, __LINE__);
            return HDF_FAILURE;
        }

        ret = i2c_driver_install(i2cPort, i2c_conf.mode, 0, 0, 0);
        if (ret != ESP_OK) {
            HDF_LOGE("%s %d i2c init fail\r\n", __func__, __LINE__);
            return HDF_FAILURE;
        }

    } else {
        HDF_LOGE("%s %d scl sda pin fail\r\n", __func__, __LINE__);
        // OsalMutexUnlock(&device->mutex);
        return HDF_ERR_INVALID_PARAM;
    }

    // OsalMutexUnlock(&device->mutex);
    return HDF_SUCCESS;
}


static int32_t AttachI2cDevice(struct I2cCntlr *host, struct HdfDeviceObject *device)
{
    int32_t ret;
    struct I2cDevice *i2cDevice = NULL;
    struct I2cResource *resource = NULL;

    if (device == NULL || host == NULL) {
        HDF_LOGE("%s: device or host is NULL\r\n", __func__);
        return HDF_ERR_INVALID_PARAM;
    }
    i2cDevice = (struct I2cDevice *)OsalMemAlloc(sizeof(struct I2cDevice));
    if (i2cDevice == NULL) {
        HDF_LOGE("%s: OsalMemAlloc i2cDevice error\r\n", __func__);
        return HDF_ERR_MALLOC_FAIL;
    }
    (void)memset_s(i2cDevice, sizeof(struct I2cDevice), 0, sizeof(struct I2cDevice));

    ret = GetI2cDeviceResource(i2cDevice, device->deviceMatchAttr);

    if (ret != HDF_SUCCESS) {
        OsalMemFree(i2cDevice);
        return HDF_FAILURE;
    }
    resource = &i2cDevice->resource;
    if (resource == NULL) {
        HDF_LOGE("%s %d: invalid parameter\r\n", __func__, __LINE__);
        return HDF_ERR_INVALID_OBJECT;
    }
    i2cDevice->port = resource->port;
    i2cDevice->mode = resource->mode;

    host->priv = i2cDevice;
    host->busId = i2cDevice->port;

    return InitI2cDevice(i2cDevice);
}


static int32_t I2cDriverInit(struct HdfDeviceObject *device)
{
    int32_t ret;
    struct I2cCntlr *host = NULL;
    if (device == NULL) {
        HDF_LOGE("%s: device is NULL\r\n", __func__);
        return HDF_ERR_INVALID_PARAM;
    }

    host = (struct I2cCntlr *)OsalMemAlloc(sizeof(struct I2cCntlr));
    if (host == NULL) {
        HDF_LOGE("%s: host is NULL\r\n", __func__);
        return HDF_ERR_MALLOC_FAIL;
    }
    (void)memset_s(host, sizeof(struct I2cCntlr), 0, sizeof(struct I2cCntlr));
    host->ops = &g_i2cHostMethod;
    device->priv = (void *)host;
    ret = AttachI2cDevice(host, device);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s: attach error\r\n", __func__);
        I2cDriverRelease(device);
        return HDF_DEV_ERR_ATTACHDEV_FAIL;
    }
    ret = I2cCntlrAdd(host);
    if (ret != HDF_SUCCESS) {
        I2cDriverRelease(device);
        return HDF_FAILURE;
    }
    return ret;
}

static int32_t I2cDriverBind(struct HdfDeviceObject *device)
{
    if (device == NULL) {
        HDF_LOGE("%s: I2c device object is NULL\r\n", __func__);
        return HDF_FAILURE;
    }
    return HDF_SUCCESS;
}

static void I2cDriverRelease(struct HdfDeviceObject *device)
{
    struct I2cCntlr *i2cCntrl = NULL;
    struct I2cDevice *i2cDevice = NULL;

    if (device == NULL) {
        HDF_LOGE("%s: device is NULL\r\n", __func__);
        return;
    }
    i2cCntrl = device->priv;
    if (i2cCntrl == NULL || i2cCntrl->priv == NULL) {
        HDF_LOGE("%s: i2cCntrl is NULL\r\n", __func__);
        return;
    }
    i2cCntrl->ops = NULL;
    i2cDevice = (struct I2cDevice *)i2cCntrl->priv;
    OsalMemFree(i2cCntrl);

    if (i2cDevice != NULL) {
        // OsalMutexDestroy(&i2cDevice->mutex);
        OsalMemFree(i2cDevice);
    }
}


static int32_t i2c_send(uint32_t i2cPort, struct I2cMsg *msg)
{
    esp_err_t ret = ESP_FAIL;

    uint16_t dataLen;
    uint16_t deviceAddr;

    dataLen = msg->len;
    deviceAddr = msg->addr;

    if (msg->flags & I2C_FLAG_READ) {

        i2c_cmd_handle_t cmd = i2c_cmd_link_create();
        ret = i2c_master_start(cmd);
        
        if (ret != ESP_OK) {
            HDF_LOGE("%s: i2c_master_start faile\r\n", __func__);
            return HDF_ERR_IO;
        }

        ret = i2c_master_write_byte(cmd, (deviceAddr << 1) | I2C_MASTER_READ, ACK_CHECK_EN);
        if (ret != ESP_OK) {
            HDF_LOGE("%s: i2c_master_write_byte faile\r\n", __func__);
            return HDF_ERR_IO;
        }
        if (dataLen > 1) {
            ret = i2c_master_read(cmd, msg->buf, dataLen - 1, ACK_VAL);
            if (ret != ESP_OK) {
                HDF_LOGE("%s: i2c_master_read faile\r\n", __func__);
                return HDF_ERR_IO;
            }
        } 

        ret = i2c_master_read_byte(cmd, msg->buf + dataLen - 1, NACK_VAL);
        if (ret != ESP_OK) {
            HDF_LOGE("%s: i2c_master_read_byte faile\r\n", __func__);
            return HDF_ERR_IO;
        }

        ret = i2c_master_stop(cmd);
        if (ret != ESP_OK) {
            HDF_LOGE("%s: i2c_master_stop faile\r\n", __func__);
            return HDF_ERR_IO;
        }

        ret = i2c_master_cmd_begin(i2cPort, cmd, 1000 / portTICK_RATE_MS);
        if (ret != ESP_OK) {
            HDF_LOGE("%s: read i2c_master_cmd_begin faile\r\n", __func__);
            return HDF_ERR_IO;
        }
        i2c_cmd_link_delete(cmd);

    } else {

        i2c_cmd_handle_t cmd = i2c_cmd_link_create();
        ret = i2c_master_start(cmd);
        if (ret != ESP_OK) {
            HDF_LOGE("%s: i2c_master_start faile\r\n", __func__);
            return HDF_ERR_IO;
        }

        ret = i2c_master_write_byte(cmd, (deviceAddr << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN);
        if (ret != ESP_OK) {
            HDF_LOGE("%s: i2c_master_write_byte faile\r\n", __func__);
            return HDF_ERR_IO;
        }

        ret = i2c_master_write(cmd, msg->buf, dataLen, ACK_CHECK_EN);
        if (ret != ESP_OK) {
            HDF_LOGE("%s: i2c_master_write faile\r\n", __func__);
            return HDF_ERR_IO;
        }

        ret = i2c_master_stop(cmd);
        if (ret != ESP_OK) {
            HDF_LOGE("%s: i2c_master_stop faile\r\n", __func__);
            return HDF_ERR_IO;
        }

        ret = i2c_master_cmd_begin(i2cPort, cmd, 1000 / portTICK_RATE_MS);
        if (ret != ESP_OK) {
            HDF_LOGE("%s: write i2c_master_cmd_begin faile\r\n", __func__);
            return HDF_ERR_IO;
        }
        
        i2c_cmd_link_delete(cmd);
    }

    return HDF_SUCCESS;
}

static int32_t i2c_transfer(struct I2cDevice *device, struct I2cMsg *msgs, int16_t count)
{
    int ret;
    struct I2cMsg *msg = NULL;
    
    uint32_t i2cPort;
    if (device == NULL || msgs == NULL) {
        HDF_LOGE("%s: device or  msgs is NULL\r\n", __func__);
        return HDF_ERR_INVALID_PARAM;
    }
    i2cPort = (uint32_t)device->port;
    if (i2cPort > HAL_I2C_ID_NUM) {
        HDF_LOGE("i2c port %u not support\r\n", i2cPort);
        return HDF_ERR_NOT_SUPPORT;
    }
    // if (HDF_SUCCESS != OsalMutexLock(&device->mutex)) {
    //     HDF_LOGE("%s %d OsalMutexTimedLock fail\r\n", __func__, __LINE__);
    //     return HDF_ERR_TIMEOUT;
    // }
    for (int32_t i = 0; i < count; i++) {
        msg = &msgs[i];
        ret = i2c_send(i2cPort, msg);
    }
    // OsalMutexUnlock(&device->mutex);
    return ret;
}

static int32_t I2cHostTransfer(struct I2cCntlr *cntlr, struct I2cMsg *msgs, int16_t count)
{
    struct I2cDevice *device = NULL;
    if (cntlr == NULL || msgs == NULL || cntlr->priv == NULL) {
        HDF_LOGE("%s: I2cCntlr or msgs is NULL\r\n", __func__);
        return HDF_ERR_INVALID_PARAM;
    }

    device = (struct I2cDevice *)cntlr->priv;
    if (device == NULL) {
        HDF_LOGE("%s: I2cDevice is NULL\r\n", __func__);
        return HDF_DEV_ERR_NO_DEVICE;
    }

    return i2c_transfer(device, msgs, count);
}
